//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

/*
 * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This file is built upon the file: src/arch/rtxc/netif/sioslip.c
 *
 * Author: Magnus Ivarsson <magnus.ivarsson(at)volvo.com>
 */

/*
 * This is an arch independent SLIP netif. The specific serial hooks must be provided
 * by another file.They are sio_open, sio_recv and sio_send
 */

#include "netif/ipif.h"
#include "lwip/tcpip.h"
#include "lwip/opt.h"
#include "lwip/def.h"
#include "lwip/pbuf.h"
#include "lwip/sys.h"
#include "lwip/stats.h"
#include "lwip/sio.h"
#include "malloc.h"
#include "arch/cc.h"


struct IPIF_LIST {
    struct netif *netif;
    struct IPIF_LIST *next;
};

static struct TCPIP_RAWIP_INTERFACE s_tcpip_rawip_interface = {
    0,
    0,
    0,
};

static int s_ipif_actived = 0;
static struct IPIF_LIST *s_ipif_list = NULL;
static void * s_ipif_lock = NULL;

static void ipif_list_lock_init()
{
    if (s_ipif_lock == NULL) {
        s_ipif_lock = mt_malloc();
    }
}

static void ipif_list_lock()
{
    if (s_ipif_lock) {
        mt_lock(s_ipif_lock);
    }
}

static void ipif_list_unlock()
{
    if (s_ipif_lock) {
        mt_unlock(s_ipif_lock);
    }
}

static void ipif_list_lock_free()
{
    if (s_ipif_lock && s_ipif_list == NULL) {
        mt_free(s_ipif_lock);
        s_ipif_lock = NULL;
    }
}

void tcpip_rawip_set_handler(
   void (*send_ip)(int id, void *buffer_ptr, unsigned long size),
   void (*recv_ip)(int *id, void **buffer_ptr_ptr, unsigned long *size),
   void (*free_ip)(void *buffer_ptr))
{
    s_tcpip_rawip_interface.send_ip = send_ip;
    s_tcpip_rawip_interface.recv_ip = recv_ip;
    s_tcpip_rawip_interface.free_ip = free_ip;
}

static int ipif_get_id_by_adapter(struct netif *netif)
{
    int id = netif->adapter[5] - '0'; // assume adapter name is "rawipX"
    if (id >= 0 && id <= 9) {
        return id;
    }
    return -1;
}

/**
 * Send a pbuf doing the necessary SLIP encapsulation
 *
 * Uses the serial layer's sio_send()
 */
err_t
ipif_output(struct netif *netif, struct pbuf *p, struct ip_addr *ipaddr)
{
    unsigned char buff[2048];
    unsigned char *mem_ptr;
    unsigned char *tmp_ptr;
    int id;
    struct pbuf *q;
    if (p == NULL) { return ERR_BUF; }
    if (p->tot_len > sizeof(buff)) {
        mem_ptr = (unsigned char *)malloc(p->tot_len);
    }
    else {
        mem_ptr = buff;
    }
    if (mem_ptr == NULL) { return ERR_MEM; }

    if (!s_tcpip_rawip_interface.send_ip) { return ERR_IF; }
    /* Send pbuf out on the peer ip I/O device. */
    tmp_ptr = mem_ptr;
    q = p;
    for (; q != NULL; tmp_ptr += q->len, q = q->next) {
        memcpy(tmp_ptr, q->payload, q->len);
    }
    id = ipif_get_id_by_adapter(netif);
    if (id != -1) {
        s_tcpip_rawip_interface.send_ip(id, mem_ptr, p->tot_len);
    }
    if (p->tot_len > sizeof(buff)) {
        free(mem_ptr);
    }
    return ERR_OK;
}

/**
 * Handle the incoming IP stream frame by frame
 *
 *
 * @return The IP packet when IP packet is received
 */
static struct pbuf *
ipif_input(int *id)
{
    struct pbuf *p, *q;
    void *buf_ptr;
    unsigned long size;
    if (s_tcpip_rawip_interface.recv_ip) {
        unsigned char *tmp_ptr;
        s_tcpip_rawip_interface.recv_ip(id, &buf_ptr, &size);
        if (buf_ptr == NULL || size == 0) { return NULL; }
        p = pbuf_alloc(PBUF_RAW, (u16_t)size, PBUF_POOL);
        if (p == NULL) { return NULL; }
        tmp_ptr = (unsigned char *)buf_ptr;
        for (q = p; q != NULL; q = q->next) {
            memcpy(q->payload, tmp_ptr, q->len);
            tmp_ptr += q->len;
        }
        LINK_STATS_INC(link.recv);
        LWIP_DEBUGF(SLIP_DEBUG, ("ipif: Got packet\n"));
        s_tcpip_rawip_interface.free_ip(buf_ptr);
        return p;
    }
    return NULL;
}

/**
 * The raw IP input thread
 *
 * Feed the IP layer with incoming packets
 */
static void
ipif_loop(void *data)
{
    while (s_ipif_actived) {
        int id;
        struct pbuf *p;
        p = ipif_input(&id);
        if (p) {
            struct IPIF_LIST *current;
            ipif_list_lock();
            current = s_ipif_list;
            while (current) {
                if (ipif_get_id_by_adapter(current->netif) == id) {
                    current->netif->input(p, current->netif);
                    break;
                }
                current = current->next;
            }
            ipif_list_unlock();
        }
    }
}

/**
 * raw IP netif initialization
 *
 * Call the arch specific sio_open and remember
 * the opened device in the state field of the netif.
 */
err_t
ipif_init(struct netif *netif)
{

    LWIP_DEBUGF(SLIP_DEBUG, ("ipif_init: netif->num=%x\n", (int)netif->num));

    netif->name[0] = 'r';
    netif->name[1] = 'i';
    netif->output = ipif_output;
    netif->mtu = 1500;
    netif->flags = NETIF_FLAG_UP;
    netif->state = (void *)1;
    if (!netif->state)
        return ERR_IF;
    return ERR_OK;
}

static void create_loop()
{
    if (s_ipif_actived == 0) {
        s_ipif_actived = 1;
        sys_thread_new(ipif_loop, NULL, SLIPIF_THREAD_PRIO);
    }
}

static void delete_loop()
{
    if (s_ipif_list == NULL) {
        s_ipif_actived = 0;
    }
}

static void ipif_connect(
    unsigned char id,
    unsigned long ipaddr, unsigned long netmask, unsigned long gw,
    unsigned long dns1, unsigned long dns2)
{
    struct netif *ipif;
    struct IPIF_LIST *ipif_list;
    struct ip_addr _ipaddr, _netmask, _gw, _dns1, _dns2;
    char adapter[10] = "rawip0";
    ipif = (struct netif *)malloc(sizeof(struct netif));
    ipif_list = (struct IPIF_LIST *)malloc(sizeof(struct IPIF_LIST));
    if (ipif == NULL) { goto FREE_AND_EXIT; }
    if (id > 9) { goto FREE_AND_EXIT; }
    adapter[5] += id;
    _ipaddr.addr = ipaddr;
    _netmask.addr = netmask;
    _gw.addr = gw;
    _dns1.addr = dns1;
    _dns2.addr = dns2;
    netif_set_default(netif_add(ipif, adapter, &_ipaddr, &_netmask, &_gw, &_dns1, &_dns2,
            NULL, ipif_init, tcpip_input));
    ipif_list->netif = ipif;
    ipif_list->next = NULL;
    ipif_list_lock_init();
    ipif_list_lock();
    if (s_ipif_list == NULL) {
        s_ipif_list = ipif_list;
    }
    else {
        ipif_list->next = s_ipif_list;
        s_ipif_list = ipif_list;
    }
    ipif_list_unlock();
    create_loop();
    return;
FREE_AND_EXIT:
    if (ipif) { free(ipif); }
    if (ipif_list) { free(ipif_list); }
}

static void ipif_disconnect(unsigned char id)
{
    struct IPIF_LIST *current, *previous;
    ipif_list_lock();
    current = s_ipif_list;
    previous = NULL;
    while (current) {
        if (ipif_get_id_by_adapter(current->netif) == id) {
            netif_remove(current->netif);
            free(current->netif);
            if (s_ipif_list == current) {
                s_ipif_list = current->next;
            }
            else {
                previous = current->next;
            }
            free(current);
            break;
        }
        previous = current;
        current = current->next;
    }
    ipif_list_unlock();
    delete_loop();
    ipif_list_lock_free();
}

struct RAWIPIF_CONTROL {
    void (*connect)(
        unsigned char id,
        unsigned long ipaddr, unsigned long netmask, unsigned long gw,
        unsigned long dns1, unsigned long dns2);
    void (*disconnect)(unsigned char id);
};

struct RAWIPIF_CONTROL g_ipif_control = {
    ipif_connect,
    ipif_disconnect,
};

